#include "routing.h"

#include <regex>
#include <cassert>

#include "common/log.h"


namespace evm { namespace http {

Rule::Rule(const string &rule, HTTPMethod method)
        : rule_(rule), method_(method) {

    static const regex part_pattern(R"re({([\w\-\.]+)(:[\w\-\.]+)?})re"); // {key:type}
    pattern_ = regex_replace(rule, part_pattern, string(R"re(([\\w\\-\\.]+))re"));
    for (sregex_iterator it(rule.begin(), rule.end(), part_pattern), end_it;
         it != end_it; ++it) {
        vars_.emplace_back(it->str(1), it->str(2));
    }

    log_info("rule_: {}, part_pattern: {}, pattern_: {}", rule_, part_pattern, pattern_);
    for (const auto& v: vars_) {
        log_info("{}: {}", v.first, v.second);
    }
}

size_t Rule::match(const string &url) const {
    log_info("url: {}, pattern: {}", url, pattern_);
    smatch m;
    if (regex_match(url, m, pattern_)) {
        log_info("match: ");
        auto n = m.size();
        if (n == vars_.size() + 1) {
            return n;  // matches + 1
        }
    }
    return 0;
}

bool Rule::match(const string &url, PathParas &paras) const {
    log_info("url: {}, pattern: {}", url, pattern_);
    smatch m;
    if (regex_match(url, m, pattern_)) {
        auto n = m.size() - 1;
        log_info("match: {}", n);
        if (n == vars_.size()) {
            for (size_t i = 0; i < n; ++i) {
                paras.emplace(vars_[i].first, m.str(static_cast<unsigned>(i + 1)));
            }
            for (const auto& v: paras) {
                log_info("{}: {}", v.first, v.second);
            }
            return true;
        }
    }
    return false;
}

Rule& Router::operator()(const string &rule, HTTPMethod method) {
    auto r = Rule(rule, method);
    for (const auto& item: rules_) {
        if (item == r) {
            log_error("Duplicate rule: {}, {}", r.rule_, r.method_);
            continue;
        }
    }
    rules_.push_back(r);
    return rules_[rules_.size() - 1];
}

Response Router::route(const string& url, Request& req) {
    log_info("route: {}", url);
    using namespace std::placeholders;

    map<size_t, const Rule*> matched_rules;
    for (const auto &r: rules_) {
        if (r.method_ == req.method) {
            auto n = r.match(url);
            if (n)
                matched_rules[n - 1] = &r;
        }
    }

    if (!matched_rules.empty()) {
        const Rule* r = matched_rules.begin()->second;

        PathParas paras;
        r->match(url, paras);
        for (const auto& p: paras)
            req.path_paras[p.first] = p.second;

        try {
            return r->callback_(req);
        } catch (const std::exception& e) {
            string err_msg = "Server exception catched: ";
            err_msg += e.what();
            log_error(err_msg);
            return Response::internal_error(err_msg.data());
        }
    }
    else
        return Response::not_found("not found");
}

void Router::add(std::initializer_list<RuleTuple> rules) {
    for (auto& rule: rules) {
        Rule& r = this->operator()(rule.url, rule.method);
        r(rule.callback);
    }
}

}}
